
#include "../utils/StringUtils.h"
#include "../iface/testerror.h"
#include "../iface/APRInterface.h"
#include "SQLiteTransactor.h"

/** Initialises the SQL transactor. This requires the table name, the
 * name of the field that is the primary key and the value that can be
 * looked up, or used to make a new record.
 * 
 * @param isqldb The SQLite database pointer.
 * 
 * @param itablename The table name of the table to use.
 * 
 * @param ipkeyfield Primary key field name.
 * 
 * @param ipkeyvalue The value in the primary key field, either in the
 * name record being written now, or in the record to be read from the
 * database. */
SQLiteTransactor::SQLiteTransactor(SQLiteDB *isqldb, const char *itablename, const char *ipkeyfield, const char *ipkeyvalue)
{
   sqldb = isqldb;
   tablename = (itablename) ? strdup(itablename) : NULL;
   pkeyfield = (ipkeyfield) ? strdup(ipkeyfield) : NULL;
   pkeyvalue = (ipkeyvalue) ? strdup(ipkeyvalue) : NULL;
   std::string fieldname(pkeyfield);
   std::string fieldvalue(pkeyvalue);
   propmap[fieldname] = std::pair<std::string, int>(fieldvalue, 1);
   uncommitted_data_not_an_error = false;
}

/** Destructor for the database record. If this record has unsaved
 * updates, an error message will be generated. */
SQLiteTransactor::~SQLiteTransactor()
{
   std::map<std::string, std::pair<std::string, int> >::iterator iter;
   int dirtycount = 0;
   std::string fieldname(pkeyfield);
   sqldb = NULL;
   if (uncommitted_data_not_an_error) goto freeall;
   propmap[fieldname].second = 0;
   for (iter = propmap.begin(); iter != propmap.end(); iter++)
      if ((iter->second.second) == 1)
      {
         errprint("uncommitted: %s='%s'", iter->first.c_str(), iter->second.first.c_str());
         dirtycount++;
      }
   if (dirtycount > 0)
   {
      errprint("destructing class while changes still outstanding in %s='%s' from table '%s'.", pkeyfield, pkeyvalue, tablename);
   }
freeall:
   if (tablename) free(tablename);
   if (pkeyfield) free(pkeyfield);
   if (pkeyvalue) free(pkeyvalue);
}

/** Get the entry in the record with the given field name.
 * 
 * @param fname Field name to get.
 * 
 * @return The text value of the field in the target record. */
const char *SQLiteTransactor::getField(const char *fname)
{
   std::string fieldname(fname);
   int elementexistcount = propmap.count(fieldname);
   if (elementexistcount == 0)
   {
      char *datapntr = NULL;
      sqldb->sqlGetSingleStringf(&datapntr,
         "select %s from '%s' where %s='%s';", fname, tablename, pkeyfield, pkeyvalue);
      if (datapntr)
      {
         std::string value(datapntr);
         propmap[fieldname] = std::pair<std::string, int>(datapntr, 0);
      }
      free(datapntr);
   }
   return propmap[fieldname].first.c_str();
}

/** Gets the table name set to this transactor.
 * 
 * @return The name of the table. */
const char *SQLiteTransactor::getTableName()
{
   return tablename;
}

/** Set the entry in the record with the given field name.
 * 
 * @param fname Field name to set.
 * 
 * @param fvalue The value of the field in the record. */
void SQLiteTransactor::setField(const char *fname, const char *fvalue)
{
   FATAL_IF_BLANK((void)0, fname, "field name");
   std::string fieldname(fname);
   std::string fieldvalue((fvalue) ? (fvalue) : "");
   propmap[fieldname] = std::pair<std::string, int>(fieldvalue, 1);
}

/** Set the entry in the record with the given field name.
 * 
 * @param fname Field name to set.
 * 
 * @param fvalue The value of the field in the record. */
void SQLiteTransactor::setField(const char *fname, const int fvalue)
{
   FATAL_IF_BLANK((void)0, fname, "field name");
   std::string fieldname(fname);
   char *newstring = StringUtils::varprintf("%d", fvalue);
   std::string fieldvalue(newstring);
   propmap[fieldname] = std::pair<std::string, int>(fieldvalue, 1);
   free(newstring);
}

/** Set the entry in the record with the given field name.
 * 
 * @param fname Field name to set.
 * 
 * @param fvalue The value of the field in the record. */
void SQLiteTransactor::setField(const char *fname, const int64_t fvalue)
{
   FATAL_IF_BLANK((void)0, fname, "field name");
   std::string fieldname(fname);
   char *newstring = StringUtils::varprintf(APR_INT64_T_FMT, fvalue);
   std::string fieldvalue(newstring);
   propmap[fieldname] = std::pair<std::string, int>(fieldvalue, 1);
   free(newstring);
}

/** Set the entry in the record with the given field name to the date
 * specified.
 * 
 * @param fname Field name to set.
 * 
 * @param fvalue The value of the field in the record. */
void SQLiteTransactor::setFieldAsTime(const char *fname, const apr_time_t fvalue)
{
   FATAL_IF_BLANK((void)0, fname, "field name");
   char datevalue[APR_RFC822_DATE_LEN + 1];
   apr_status_t status;
   status = apr_rfc822_date(datevalue, fvalue);
   CheckAPRError(status);
   if (status != APR_SUCCESS)
   {
      errprint("could not convert date to string");
      return;
   }
   std::string fieldname(fname);
   std::string fieldvalue(datevalue);
   propmap[fieldname] = std::pair<std::string, int>(fieldvalue, 1);
}

/** Set the entry in the record with the given field name to the
 * character specified, or blank if the character is \0.
 * 
 * @param fname Field name to set.
 * 
 * @param fvalue The value of the field in the record. */
void SQLiteTransactor::setField(const char *fname, const char fvalue)
{
   FATAL_IF_BLANK((void)0, fname, "field name");
   char tinystring[2];
   tinystring[0] = fvalue;
   tinystring[1] = '\0';
   std::string fieldname(fname);
   std::string fieldvalue(tinystring);
   propmap[fieldname] = std::pair<std::string, int>(fieldvalue, 1);
}

/** Update the copy of the record in the database - any modified field
 * entries are flushed via SQL UPDATE.  This is different to allow for
 * hooks by derived classes. */
void SQLiteTransactor::update()
{
   do_update();
}

/** Update the copy of the record in the database - any modified field
 * entries are flushed via SQL UPDATE. */
void SQLiteTransactor::do_update()
{
   char *current_string = StringUtils::varprintf("update '%s' set ", tablename);
   std::map<std::string, std::pair<std::string, int> >::iterator iter;
   int dirtycount = 0;
   for (iter = propmap.begin(); iter != propmap.end(); iter++)
   {
      if ((iter->second.second) == 1)
      {
         char *old_string_copy = current_string;
         current_string = StringUtils::varprintf("%s%s='%s',", old_string_copy, iter->first.c_str(), iter->second.first.c_str());
         free(old_string_copy);
         dirtycount++;
      }
   }
   if (dirtycount == 0)
   {
      errprint("nothing new to update in record %s='%s' in %s", pkeyfield, pkeyvalue, tablename);
      free(current_string);
      return;
   }
   current_string[strlen(current_string) - 1] = '\0';
   char *old_string_copy = current_string;
   current_string = StringUtils::varprintf("%s where %s='%s';", old_string_copy, pkeyfield, pkeyvalue);
   free(old_string_copy);
   int retval = sqldb->sqlInstantExecf(current_string);
   free(current_string);
   if (retval != 0)
      errprint("problem transacting the database update");
   if (retval == 0)
      for (iter = propmap.begin(); iter != propmap.end(); iter++)
         iter->second.second = 0;
}

/** Creates a new entry in the database table with the primary key as
 * given in the constructor. Unset fields are set to empty strings.
 * This is different to allow for hooks by derived classes. */
void SQLiteTransactor::commit()
{
   do_commit();
}

/** Creates a new entry in the database table with the primary key as
 * given in the constructor. Unset fields are set to empty strings. */
void SQLiteTransactor::do_commit()
{
   char *old_string_copy = NULL;
   std::vector<char *> fieldnames;
   std::vector<char *> fieldtypes;
   loadTableDefinition(tablename, fieldnames, fieldtypes);
   if (fieldnames.size() == 0)
   {
      errprint("no fields defined");
      return;
   }
   // now we insert all of these as a new record
   char *current_string = StringUtils::varprintf("replace into '%s' (", tablename);
   for (int i = 0; i < fieldnames.size(); i++)
   {
      old_string_copy = current_string;
      current_string = StringUtils::varprintf("%s%s,", old_string_copy, fieldnames.at(i));
      free(old_string_copy);
   }
   old_string_copy = current_string;
   current_string[strlen(current_string) - 1] = '\0';
   current_string = StringUtils::varprintf("%s) values (", old_string_copy);
   free(old_string_copy);
   for (int i = 0; i < fieldnames.size(); i++)
   {
      char *old_string_copy = current_string;
      current_string = StringUtils::varprintf("%s'%s',", old_string_copy, propmap[fieldnames.at(i)].first.c_str());
      free(old_string_copy);
      free(fieldnames.at(i));
      free(fieldtypes.at(i));
   }
   old_string_copy = current_string;
   current_string[strlen(current_string) - 1] = '\0';
   current_string = StringUtils::varprintf("%s);", old_string_copy);
   free(old_string_copy);
   int retval = sqldb->sqlInstantExecf(current_string);
   free(current_string);
   std::map<std::string, std::pair<std::string, int> >::iterator iter;
   if (retval != 0)
      errprint("problem transacting the database commit");
   if (retval == 0)
      for (iter = propmap.begin(); iter != propmap.end(); iter++)
         iter->second.second = 0;
}

/** Pre-cache the whole record, as it's held in the database. */
void SQLiteTransactor::precache()
{
   char *old_string_copy = NULL;
   std::vector<char *> fieldnames;
   std::vector<char *> fieldtypes;
   loadTableDefinition(tablename, fieldnames, fieldtypes);
   char *current_string = StringUtils::varprintf("select ", tablename);
   for (int i = 0; i < fieldnames.size(); i++)
   {
      old_string_copy = current_string;
      current_string = StringUtils::varprintf("%s%s,", old_string_copy, fieldnames.at(i));
      free(old_string_copy);
   }
   old_string_copy = current_string;
   current_string[strlen(current_string) - 1] = '\0';
   current_string = StringUtils::varprintf("%s) from '%s' where %s='%s';",
      old_string_copy, tablename, pkeyfield, pkeyvalue);
   free(old_string_copy);
   std::vector<char *> *data = sqldb->sqlScanRowText(current_string);
   free(current_string);
   if (!data)
   {
      errprint("record caching failed");
      return;
   }
   if (data->size() != fieldnames.size())
   {
      errprint("field number mismatch, wanted %d fields, got %d fields", (int)(fieldnames.size()), (int)(data->size()));
      delete data;
      return;
   }
   for (int i = 0; i < fieldnames.size(); i++)
   {
      std::string fname(fieldnames.at(i));
      std::string value(data->at(i));
      propmap[fname] = std::pair<std::string, int>(value, 0);
   }
}

/** Get the record definition for the table.
 * 
 * @param itablename The name of the table whose definitions are to be
 * loaded.
 * 
 * @param fieldnames The names of the fields in any record in this
 * table.
 * 
 * @param fieldtypes The SQL types of the fields in any record in this
 * table. */
void SQLiteTransactor::loadTableDefinition(const char *itablename, std::vector<char *> &fieldnames, std::vector<char *> &fieldtypes)
{
   FATAL_IF_BLANK((void)0, itablename, "table name");
   // Watch out! This may protect against grepping substrings, but may
   // not be portable across SQL engines.
   char *newtablename = StringUtils::varprintf("'%s'", itablename);
   char *sql_line = NULL;
   sqldb->sqlGetSingleStringf(&sql_line, "select sql from 'sqlite_master' where type='table' and tbl_name='%s' limit 1;", itablename);
   if (sql_line)
   {
      char *sql_line_pbegin = &(strstr(sql_line, newtablename)[strlen(newtablename)]);
      while ((sql_line_pbegin[0] != '(') && (sql_line_pbegin[0] != '\0'))
         sql_line_pbegin = &(sql_line_pbegin[1]);
      parseTableDefinition(sql_line_pbegin, fieldnames, fieldtypes);
      free(sql_line);
   }
   free(newtablename);
}

/** Parse the results from the SQL query into the field names and
 * types.
 * 
 * @param table_definition The string definition for the tables, as
 * returned from the XML query.
 * 
 * @param fieldnames The names of the fields in any record in this
 * table.
 * 
 * @param fieldtypes The SQL types of the fields in any record in this
 * table. */
void SQLiteTransactor::parseTableDefinition(char *table_definition, std::vector<char *> &fieldnames, std::vector<char *> &fieldtypes)
{
   FATAL_IF_BLANK((void)0, table_definition, "table definition string");
   std::vector<char> collected;
   char *curr = &(table_definition[1]);
   int bractno = 1;
   int istype = 0;
   do
   {
      if ((curr[0] == ' ') && (istype == 0))
      {
         if (collected.size() > 0)
         {
            collected.push_back('\0');
            fieldnames.push_back(strdup(&(collected.at(0))));
         }
         collected.clear();
         istype = 1;
      } else if ((curr[0] == ',') && (bractno == 1) && (istype == 1))
      {
         if (collected.size() > 0)
         {
            collected.push_back('\0');
            fieldtypes.push_back(strdup(&(collected.at(0))));
         }
         collected.clear();
         istype = 2;
      } else if (istype == 2)
      {
         istype = 0;
      } else
      {
         collected.push_back(curr[0]);
      }
      if (curr[0] == '(') bractno++;
      if (curr[0] == ')') bractno--;
      curr = &(curr[1]);
   } while ((bractno > 0) && (curr[0] != '\0'));
   if (collected.size() > 0)
   {
      collected.at(collected.size() - 1) = '\0';
      fieldtypes.push_back(strdup(&(collected.at(0))));
   }
}

/** Set a do-not-save flag so that not saving the data on destruction
 * is perceived as okay. */
void SQLiteTransactor::doNotSave()
{
   uncommitted_data_not_an_error = true;
}
